﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Perceiveit.Xml
{
    /// <summary>
    /// A multicast factory to instaniate differing subclasses based on 
    /// discreet XNames and return the names based on the subclass types.
    /// See the XFactory class for a description of this interface
    /// </summary>
    /// <typeparam name="T">The base class type that this factory creates instances of</typeparam>
    /// <remarks>Each inner factory will be checked in the order specified by the ChoiceOrder - therefore a later addition can override the behaviour of an existing XFactory</remarks>
    public class XChoiceFactory<T> : XFactory<T> where T:XEntity
    {
        #region ivars

        /// <summary>
        /// The contained list of inner XFactory's
        /// </summary>
        private List<XFactory<T>> _list = new List<XFactory<T>>();
        private ChoiceOrder _order = ChoiceOrder.FirstInFirstCheck;

        #endregion

        //
        // public properties
        //

        #region public ChoiceOrder ChoiceOrder {get;set;}

        /// <summary>
        /// Gets or sets the order with which each inner factory will be checked
        /// i.e FirstInFirstCheck - factory at index 0 will be checked before factory at index 1
        /// </summary>
        public ChoiceOrder ChoiceOrder
        {
            get { return _order; }
            set { _order = value; }
        }

        #endregion


        #region public XFactory<T> this[int index] {get;}

        /// <summary>
        /// Gets the XFactory at the specified index
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public XFactory<T> this[int index]
        {
            get { return this._list[index]; }
        }

        #endregion

        #region public int Count {get;}

        /// <summary>
        /// Gets the number of inner factories in this XChoiceFactory
        /// </summary>
        public int Count
        {
            get { return this._list.Count; }
        }

        #endregion

        //
        // ctors
        //

        #region public XChoiceFactory()

        /// <summary>
        /// Creates a new (empty) XChoiceFactory, 
        /// where specific XFactory instances can be added later 
        /// </summary>
        public XChoiceFactory()
        {
        }

        #endregion


        #region public XChoiceFactory(params XFactory<T>[] factories)

        /// <summary>
        /// Creates a new XChoiceFactory containing the set subclass factories.
        /// </summary>
        /// <param name="factories">A set of subclass factories to use</param>
        public XChoiceFactory(params XFactory<T>[] factories)
            : this((IEnumerable<XFactory<T>>)factories)
        {
        }

        #endregion


        #region public XChoiceFactory(IEnumerable<XFactory<T>> factories)

        /// <summary>
        /// Creates a new instance of the XChoiceFactory containing the set of subclass 
        /// factories specified in the enumerable set
        /// </summary>
        /// <param name="factories"></param>
        public XChoiceFactory(IEnumerable<XFactory<T>> factories) : this()
        {
            if(null != factories)
                this.AddRange(factories);
        }

        #endregion


        //
        // public methods
        //

        #region public void Add(XFactory<T> factory)

        /// <summary>
        /// Adds an XFactory to the set of choices
        /// </summary>
        /// <param name="factory"></param>
        public void Add(XFactory<T> factory)
        {
            _list.Add(factory);
        }

        #endregion

        #region public void AddRange(IEnumerable<XFactory<T>> factories)

        /// <summary>
        /// Adds all the XFactories in the parameter to the set of choices
        /// </summary>
        /// <param name="factories"></param>
        public void AddRange(IEnumerable<XFactory<T>> factories)
        {
            _list.AddRange(factories);
        }

        #endregion

        #region public override bool IsKnownName(System.Xml.Linq.XName name, out XFactory<T> factory)

        /// <summary>
        /// Overrides the base method to check the inner factories.
        /// Returns true if any of the inner factories know the name and return true
        /// </summary>
        /// <param name="name">The name to check</param>
        /// <param name="factory">Set to the child XFactory which knows about this XName</param>
        /// <returns>True if one of the inner factories return true for the specified name</returns>
        public override bool IsKnownName(System.Xml.Linq.XName name, out XFactory<T> factory)
        {
            XFactory<T> found;
            if (this.TryGetFactoryForName(name, out found))
            {
                factory = found;
                return true;
            }
            else
            {
                factory = null;
                return false;
            }
        }

        #endregion 

        #region public override bool IsKnownEntity(T entity, out XFactory<T> factory)

        /// <summary>
        /// Overrides the base method to check the inner factories.
        /// Returns true if any of the inner factories know the entity and return true
        /// </summary>
        /// <param name="entity">The entity to check</param>
        /// <param name="factory">Set to the factory that 'knows' the entity type or null</param>
        /// <returns>True if a child factory reported that it knew the XEntity type</returns>
        public override bool IsKnownEntity(T entity, out XFactory<T> factory)
        {
            XFactory<T> found;
            if (this.TryGetFactoryForEntity(entity, out found))
            {
                factory = found;
                return true;
            }
            else
            {
                factory = null;
                return false;
            }
        }

        #endregion

        #region public override System.Xml.Linq.XName GetNameForEntity(T entity)

        /// <summary>
        /// Overrides the base method to check the inner factories.
        /// Returns the XName for a specific entity.
        /// </summary>
        /// <param name="entity">The entity to get the name for</param>
        /// <returns>An XName for this entity</returns>
        public override System.Xml.Linq.XName GetNameForEntity(T entity)
        {
            XFactory<T> found;
            if (this.TryGetFactoryForEntity(entity, out found))
                return found.GetNameForEntity(entity);
            else
                return null;
        }

        #endregion

        #region public override T GetEntityForName(System.Xml.Linq.XName name)

        /// <summary>
        /// Overrides the base method to check the inner factories.
        /// Returns a new un-initialized entity for the specified name
        /// </summary>
        /// <param name="name">The name to get the entity for</param>
        /// <returns>A new un-initialized instance of the entity that corresponds to the XName</returns>
        public override T GetEntityForName(System.Xml.Linq.XName name)
        {
            XFactory<T> found;
            if (this.TryGetFactoryForName(name, out found))
                return found.GetEntityForName(name);
            else
                return null;
        }

        #endregion

        //
        // protected implementation
        //

        #region protected virtual bool TryGetFactoryForName(XName name, out XFactory<T> factory)

        /// <summary>
        /// Virtual method that checks this instances child factories in 
        /// the order specified by ChoiceOrder, to find one that returns
        /// true as a known name
        /// </summary>
        /// <param name="name">The name to find the factory for</param>
        /// <param name="factory">Set to the found factory (if any)</param>
        /// <returns>True if a factory was found otherwise false</returns>
        protected virtual bool TryGetFactoryForName(XName name, out XFactory<T> factory)
        {
            if (this.ChoiceOrder == ChoiceOrder.FirstInFirstCheck)
            {
                XFactory<T> found;
                foreach (XFactory<T> inner in this._list)
                {
                    if (inner.IsKnownName(name, out found))
                    {
                        factory = found;
                        return true;
                    }
                }
            }
            else
            {
                XFactory<T> found;
                for (int i = this._list.Count - 1; i >= 0; i--)
                {
                    XFactory<T> item = this._list[i];

                    if (item.IsKnownName(name, out found))
                    {
                        factory = found;
                        return true;
                    }
                }
            }

            //not found
            factory = null;
            return false;
        }

        #endregion

        #region protected virtual bool TryGetFactoryForEntity(T entity, out XFactory<T> factory)

        /// <summary>
        /// Virtual method that checks this instances child factories in 
        /// the order specified by ChoiceOrder, to find one that returns
        /// true as a known entity
        /// </summary>
        /// <param name="entity">The XEntity to check and find a factory for</param>
        /// <param name="factory">Set to the found factory (or null if no factory was found)</param>
        /// <returns>True if a factory was found otherwise false</returns>
        protected virtual bool TryGetFactoryForEntity(T entity, out XFactory<T> factory)
        {
            XFactory<T> found;
            if (this.ChoiceOrder == ChoiceOrder.FirstInFirstCheck)
            {
                foreach (XFactory<T> item in this._list)
                {
                    if (item.IsKnownEntity(entity, out found))
                    {
                        factory = found;
                        return true;
                    }
                }
            }
            else
            {
                for (int i = this._list.Count - 1; i >= 0; i--)
                {
                    XFactory<T> item = this._list[i];

                    if (item.IsKnownEntity(entity, out found))
                    {
                        factory = found;
                        return true;
                    }
                }
            }

            factory = null;
            return false;
        }

        #endregion
    }
        
}
